﻿/*
 * Copyright 2011 Hanns Kucer
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Threading;
using System.Net.Sockets;
using System.Text;

namespace iTachToolkit
{

    internal class TubeDispatcher
    {
        internal static DateTime ServerUpSince = DateTime.Now;
        internal static volatile uint ConnectionTurnover = 0;
        internal static volatile uint RequestTurnover = 0;

        public delegate void OnRequestCallback(Tube tube);

        private class SocketTube : Tube
        {
            protected TcpClient client;
            protected int port = 0;

            public SocketTube(int port)
                : base()
            {
                this.port = port;
            }

            public SocketTube(TcpClient client)
            {
                this.client = client;
            }

            public override bool IsConnected
            {
                get { return client != null && client.Connected; }
            }

            protected override System.IO.Stream Stream
            {
                get { return IsConnected ? client.GetStream() : null; }
            }

            public override void Disconnect()
            {
                if (IsConnected)
                    client.Close();
            }
        }

        public static Tube iTachTube(string host, int port)
        {
            var t = new SocketClientTube(host, port);
            t.Connect();
            return t;
        }

        private class SocketClientTube : SocketTube
        {
            private string host;

            public SocketClientTube(string host, int port)
                : base(port)
            {
                this.host = host;
            }

            public SocketClientTube(int port)
                : this("127.0.0.1", port)
            {
            }

            public bool Connect()
            {
                client = new TcpClient(host, port);
                return IsConnected;
            }
        }

        private TcpListener _listener;
        private Thread _worker;
        private MonitoredThreadSafeQueue<Tube> connections;
        private MonitoredThreadSafeQueue<int> sps;

        private int maxClients;

        private EndPoint.IpEndPoint endPoint;

        public TubeDispatcher(EndPoint.IpEndPoint endPoint, int maxClients)
        {
            this.endPoint = endPoint;
            this.maxClients = maxClients;

            sps = new MonitoredThreadSafeQueue<int>(maxClients);

            for (int i = 0; i < maxClients; i++)
            {
                sps.Enqueue(1);
            }

            connections = new MonitoredThreadSafeQueue<Tube>(maxClients);
        }

        public TubeDispatcher(EndPoint.IpEndPoint endPoint)
            : this(endPoint, 8)
        {
        }

        public Tube WaitForConnection()
        {
            Tube t = connections.Dequeue();
            ConnectionTurnover++;
            return t;
        }

        public virtual void Return(Tube tube)
        {
            tube.Disconnect();
            sps.Enqueue(1);
        }

        public void Start()
        {
            System.Net.IPAddress hostIP = System.Net.Dns.GetHostEntry(endPoint.Host).AddressList[0];
            _listener = new TcpListener(new System.Net.IPEndPoint(hostIP, endPoint.Port));
            _worker = new Thread(_workerproc);
            _worker.Start();
        }

        public void Stop()
        {
            _listener.Stop();
            _worker.Join();
        }

        private void _workerproc(object objState)
        {
			Thread.CurrentThread.Name = GetType().Name;

            _listener.Start(maxClients);

            while (true)
            {
                sps.Dequeue();

                try
                {
                    connections.Enqueue(new SocketTube(_listener.AcceptTcpClient()));
                }
                catch (SocketException)
                {
                    // listener has been stopped so we stop
                    break;
                }
            }

            // signal stop
            connections.Enqueue(null);
        }
    }
}